
#include "nane/InputSystem/InputBindingImpl_.h"
#include "nane/core/assert.h"

#include <algorithm>
#include <cmath>

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    InputBindingImpl::InputBindingImpl( const InputBinding& _inputBinding, uint32 _modifiersCount, const InputBindingModifier* _modifiers )
        : m_deviceNumAxisHash( (_inputBinding.deviceType.raw_value() << 16) | (_inputBinding.deviceNum << 8) | _inputBinding.axis )
        , m_axisValueLast( 0 )
        , m_axisValueAccumulated( 0 )
        , m_eventsNum( 0 )
        , m_scale( _inputBinding.scale )
        , m_treshold( _inputBinding.treshold )
        , m_wasPressed( 0 )
        , m_wasReleased( 0 )
        , m_allModifiersPressed( false )
        , m_modifiersStateOutOfDate( true )
    {
        m_modifiers.resize(_modifiersCount);
        for( uint32 i = 0; i < _modifiersCount; ++i )
        {
            Modifier& modifier = m_modifiers[i];
            modifier.deviceNumAxisHash = (_modifiers[i].deviceType.raw_value() << 16) | (_modifiers[i].deviceNum << 8 ) | _modifiers[i].axis;
            modifier.isPressed = false;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    InputBindingImpl::~InputBindingImpl()
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    InputBindingImpl& InputBindingImpl::operator=( const InputBinding& _inputBinding )
    {
        m_deviceNumAxisHash = (_inputBinding.deviceType.raw_value() << 16) | (_inputBinding.deviceNum << 8) | _inputBinding.axis;
        m_scale = _inputBinding.scale;
        m_treshold = _inputBinding.treshold;
        return *this;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool InputBindingImpl::operator==( const InputBinding& _inputBinding )
    {
        return m_deviceNumAxisHash == ((_inputBinding.deviceType.raw_value() << 16) | (_inputBinding.deviceNum << 8) | _inputBinding.axis);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void InputBindingImpl::OnFrameBegin()
    {
        m_axisValueAccumulated = m_axisValueLast;
        m_eventsNum = 0;
        m_wasPressed = 0;
        m_wasReleased = 0;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void InputBindingImpl::OnInputEvent( EInputDevice _inputDevice, InputEvent* _event )
    {
        NANE_ASSERT_R( _event != NULL, "InputEvent must be valid" );
        uint32 deviceNumAxisHash = (_inputDevice.raw_value() << 16) | (_event->deviceNum << 8) | _event->axis;
        if( m_deviceNumAxisHash == deviceNumAxisHash )
        {
            
            int32 value = ((std::abs(_event->value) > m_treshold) && (AllModifiersPressed_() == true)) ? _event->value : 0;
            if( value == m_axisValueLast )
            {
                return;
            }
            uint32 firstEventFactor = std::min<uint32>(m_eventsNum, 1);
            if( m_axisValueLast == 0 && _event->value > 0 )
            {
                m_wasPressed += 1;
            }
            else if( m_axisValueLast > 0 && _event->value == 0 )
            {
                m_wasReleased += 1;
            }
            m_axisValueLast = _event->value;
            m_axisValueAccumulated = firstEventFactor * m_axisValueAccumulated;
            m_axisValueAccumulated = m_axisValueAccumulated + m_axisValueLast;  // TODO: add value overflow check
            m_eventsNum += 1;
        }
        else if( m_modifiers.empty() == false )
        {
            for( std::vector<Modifier>::iterator it = m_modifiers.begin(), it_end = m_modifiers.end();
                it != it_end;
                ++it )
            {
                Modifier& modifier = (*it);
                if( modifier.deviceNumAxisHash == deviceNumAxisHash )
                {
                    modifier.isPressed = (_event->value > 0);
                    m_modifiersStateOutOfDate = true;
                }
            }
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    float InputBindingImpl::GetLastValue()
    {
        return m_axisValueLast * m_scale;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    float InputBindingImpl::GetAverageValue()
    {
        return static_cast<float>(m_axisValueAccumulated) / std::max<uint32>(m_eventsNum, 1) * m_scale;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 InputBindingImpl::WasPressed()
    {
        return m_wasPressed;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool InputBindingImpl::IsPressed()
    {
        return m_axisValueLast != 0;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 InputBindingImpl::WasReleased()
    {
        return m_wasReleased;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool InputBindingImpl::IsReleased()
    {
        return m_axisValueLast == 0;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool InputBindingImpl::AllModifiersPressed_()
    {
        if( m_modifiersStateOutOfDate == true )
        {
            m_allModifiersPressed = true;
            for( std::vector<Modifier>::iterator it = m_modifiers.begin(), it_end = m_modifiers.end();
                it != it_end;
                ++it )
            {
                if( (*it).isPressed == false )
                {
                    m_allModifiersPressed = false;
                    break;
                }
            }
            m_modifiersStateOutOfDate = false;
        }
        return m_allModifiersPressed;
    }
}   // namespace nane
